﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Xml.Linq;
using System.Threading;
using System.Net;
using System.ServiceModel.Channels;
using System.Globalization;
using System.IO;
using ThreeByte.ActiveDeck;
using ThreeByte.ActiveDeck.Publish.Model;
using ThreeByte.ActiveDeck.Publish;
using System.ServiceModel.Activation;
using ThreeByte.ActiveDeck.Service;
using System.Web.Security;
using System.Text.RegularExpressions;

namespace ThreeByte.ActiveDeck.Web
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class ActiveDeckSyncService : IActiveDeckSyncService
    {
        private static readonly XNamespace ns = ServiceConfiguration.NAMESPACE;

        private static IPublisher ACTIVE_PUB = ActivePublisher.Instance;
        private static TimeSpan SERVER_POLL_TIMEOUT = TimeSpan.FromSeconds(60);

        public IAsyncResult BeginPublish(string presentation, XElement body, AsyncCallback callback, object state) {

            try {
                XElement auth = body.Element(ns + "Authentication");
                MembershipUser user = ActiveDeckConfigService.Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return new CompletedAsyncResult<XElement>(ActiveDeckConfigService.ErrorMessage("User is not authorized to publish messages on this channel"), callback, state);
                }

                XElement messageNode = body.Element(ns + "Message");
                ActiveMessage message = ActiveMessage.FromXml(messageNode);

                if(!message.Channel.StartsWith(string.Format("{0}_", presentation))) {
                    return new CompletedAsyncResult<XElement>(ActiveDeckConfigService.ErrorMessage("Invalid channel for presentation"), callback, state);
                }
                return ACTIVE_PUB.BeginPublishMessage(message.Channel, message.Body, callback, state);
            } catch(Exception e) {
                //TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.BadRequest);
            }

        }

        public XElement EndPublish(IAsyncResult result) {
            if(result is CompletedAsyncResult<XElement>) {
                return ((CompletedAsyncResult<XElement>)result).Result;
            }
            
            int messageId;
            try {
                messageId = ACTIVE_PUB.EndPublishMessage(result);
            } catch(Exception e) {
                // TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.InternalServerError);
            }
            XElement response = new XElement(ns + "ActiveUpdate");
            XElement pubNode = new XElement(ns + "Published", new XAttribute("MessageID", messageId));
            response.Add(pubNode);
            return response;
        }

        public IAsyncResult BeginSubscribe(XElement body, AsyncCallback callback, object state) {

            string presPattern = @"(\d{6})_.+";

            try {
                //Check Authentication here
                XElement auth = body.Element(ns + "Authentication");
                MembershipUser user = ActiveDeckConfigService.Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                Dictionary<string, int> subs = ParseSubscriptions(body.Element(ns + "Subscribe"));
                foreach(string channel in subs.Keys) {
                    Match presMatch = Regex.Match(channel, presPattern);
                    if(!presMatch.Success) {
                        return new CompletedAsyncResult<XElement>(ActiveDeckConfigService.ErrorMessage(string.Format("Invalid channel format: {0}", channel)), callback, state);
                    }
                    string presentation = presMatch.Groups[1].Value;

                    if(!(AuthorizationUtil.IsAuthorizedParticipant(presentation, user.UserName))) {
                        return new CompletedAsyncResult<XElement>(ActiveDeckConfigService.ErrorMessage(string.Format("User is not authorized to subscribe to messages on this channel: {0}", channel)), callback, state);
                    }
                }

                return ACTIVE_PUB.BeginSubscribe(subs, SERVER_POLL_TIMEOUT, callback, state);
            } catch(Exception e) {
                // TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.BadRequest);
            }
        }


        public XElement EndSubscribe(IAsyncResult result) {
            if(result is CompletedAsyncResult<XElement>) {
                return ((CompletedAsyncResult<XElement>)result).Result;
            }

            IEnumerable<ActiveMessage> messages;
            try {
                messages = ACTIVE_PUB.EndSubscribe(result);
            } catch(Exception e) {
                // TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.InternalServerError);
            }
            XElement updateNode = new XElement(ns + "ActiveUpdate");

            if(messages != null) {
                foreach(ActiveMessage m in messages) {
                    updateNode.Add(m.ToXml());
                }

            }
            return updateNode;
        }


        public IAsyncResult BeginGetChannelList(string presentation, XElement body, AsyncCallback callback, object state) {

            try {
                XElement auth = body.Element(ns + "Authentication");
                MembershipUser user = ActiveDeckConfigService.Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return new CompletedAsyncResult<XElement>(ActiveDeckConfigService.ErrorMessage("User is not authorized to publish messages on this channel"), callback, state);
                }

                return ACTIVE_PUB.BeginGetChannels(presentation, callback, state);
            } catch(Exception e) {
                //TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.BadRequest);
            }

        }

        public XElement EndGetChannelList(IAsyncResult result) {
            if(result is CompletedAsyncResult<XElement>) {
                return ((CompletedAsyncResult<XElement>)result).Result;
            }

            Dictionary<string, int> channels;
            try {
                channels = ACTIVE_PUB.EndGetChannels(result);
            } catch(Exception e) {
                // TODO formal exception contract is needed
                throw new WebFaultException<string>(e.ToString(), HttpStatusCode.InternalServerError);
            }
            XElement response = new XElement(ns + "ActiveUpdate");
            foreach(string s in channels.Keys.OrderBy(p => p)) {
                XElement channelNode = new XElement(ns + "Channel", new XAttribute("ID", s), new XAttribute("Message", channels[s]));
                response.Add(channelNode);
            }
            
            return response;
        }

        private Dictionary<string, int> ParseSubscriptions(XElement subscribeNode) {
            Dictionary<string, int> subs = new Dictionary<string, int>();

            foreach(XElement c in subscribeNode.Elements(ns + "Channel")) {
                int fromInt = 0;
                int.TryParse(c.Attribute("From").Value, out fromInt);
                subs[c.Attribute("ID").Value] = fromInt;
            }

            return subs;
        }


    }
}
